package g_mysql

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"log"
	"os"
	"path"
	"strings"
	"sync"
	"time"
)

/**参见 https://github.com/jinzhu/gorm**/

var (
	MYSQL_ERR_BUILDER = errors.New("mysql builder error")
	DEFULA_LOG_DIR    = "/Log"
	UTFALL_DATE       = "2006-01-02"
	cstZone           = time.FixedZone("CST", 8*3600)
)

/*
*
带参初始化
*/
func NewMysqlBuilder(builderName, host string, port int, user, pwd, dbName, chartSet string, debug int) BuilderInterface {
	mysqlBuilder := &mysqlBuilder{
		builderName: builderName,
		host:        host,
		port:        port,
		user:        user,
		pwd:         pwd,
		dbName:      dbName,
		chartSet:    chartSet,
		isDebug:     debug,
	}
	return mysqlBuilder
}

/*
*
无参数初始化
*/
func NewMysqlBuilderEm() BuilderInterface {
	return &mysqlBuilder{}
}

type mysqlBuilder struct {
	builderName  string
	host         string
	port         int
	user         string
	pwd          string
	dbName       string
	chartSet     string
	isDebug      int
	maxIdleConns int //设置空闲连接池中的最大连接数
	maxOpenConns int //设置与数据库的最大打开连接数

	logDir string //日志保存目录(当isDebug为true时开启)
}

func (mb *mysqlBuilder) SetBuilderName(builderName string) BuilderInterface {
	mb.builderName = builderName
	return mb
}

func (mb *mysqlBuilder) GetBuildName() string {
	return mb.builderName
}

func (mb *mysqlBuilder) SetHost(host string) BuilderInterface {
	mb.host = host
	return mb
}

func (mb *mysqlBuilder) GetHost() string {
	return mb.host
}

func (mb *mysqlBuilder) SetPort(port int) BuilderInterface {
	mb.port = port
	return mb
}
func (mb *mysqlBuilder) GetPort() int {
	return mb.port
}

func (mb *mysqlBuilder) SetUser(user string) BuilderInterface {
	mb.user = user
	return mb
}

func (mb *mysqlBuilder) GetUser() string {
	return mb.user
}

func (mb *mysqlBuilder) SetPwd(pwd string) BuilderInterface {
	mb.pwd = pwd
	return mb
}
func (mb *mysqlBuilder) GetPwd() string {
	return mb.pwd
}

func (mb *mysqlBuilder) SetDbName(dbName string) BuilderInterface {
	mb.dbName = dbName
	return mb
}

func (mb *mysqlBuilder) GetDbName() string {
	return mb.dbName
}

func (mb *mysqlBuilder) SetChartSet(chartSet string) BuilderInterface {
	mb.chartSet = chartSet
	return mb
}
func (mb *mysqlBuilder) GetChartSet() string {
	return mb.chartSet
}
func (mb *mysqlBuilder) SetIsDebug(isDebug int) BuilderInterface {
	mb.isDebug = isDebug
	return mb
}

func (mb *mysqlBuilder) GetIsDebug() int {
	return mb.isDebug
}

func (mb *mysqlBuilder) SetMaxIdleConns(maxIdleConns int) BuilderInterface {
	mb.maxIdleConns = maxIdleConns
	return mb
}

func (mb *mysqlBuilder) GetMaxIdleConns() int {
	return mb.maxIdleConns
}

func (mb *mysqlBuilder) SetMaxOpenConns(maxOpenConns int) BuilderInterface {
	mb.maxOpenConns = maxOpenConns
	return mb
}

func (mb *mysqlBuilder) GetMaxOpenConns() int {
	return mb.maxOpenConns
}

func (mb *mysqlBuilder) SetLogDir(logDir string) BuilderInterface {
	mb.logDir = logDir
	return mb
}

func (mb *mysqlBuilder) GetLogDir() string {
	return mb.logDir
}

func (mb *mysqlBuilder) GetHash() (string, error) {
	//keyHash := Hash256(fmt.Sprintf("%s%d%s", mb.GetHost(), mb.GetPort(), mb.GetDbName()))
	if len(strings.TrimSpace(mb.builderName)) == 0 {
		return "", errors.New("builder name was not empty")
	}
	keyHash := hash256(mb.GetBuildName())
	return keyHash, nil
}

var mysqlOnce sync.Once
var mysqlInstance *mysqlPool

func NewMysqlPool() *mysqlPool {
	mysqlOnce.Do(func() {
		mysqlInstance = &mysqlPool{
			builders: []BuilderInterface{},
			conn:     make(map[string]*gorm.DB),
			glog:     GetLogger(),
		}
	})
	return mysqlInstance
}

type mysqlPool struct {
	builders []BuilderInterface
	conn     map[string]*gorm.DB
	glog     *logrus.Logger
}

func (mp *mysqlPool) SetBuilder(builder BuilderInterface) {
	mp.builders = append(mp.builders, builder)
}

func (mp *mysqlPool) SetBuilders(builders ...BuilderInterface) {
	mp.builders = builders
}

func (mp *mysqlPool) Init() error {
	if mp.builders != nil && len(mp.builders) > 0 {
		for _, v := range mp.builders {
			err := mp.initializationMysql(v)
			if err != nil {
				return err
			}
		}
	} else {
		return MYSQL_ERR_BUILDER
	}

	return nil

}

func (mp *mysqlPool) initializationMysql(builder BuilderInterface) error {
	//如果已存在则不加入
	bHansh, err := builder.GetHash()
	if err != nil {
		return err
	}
	if _, ok := mp.conn[bHansh]; ok {
		return nil
	}
	dns := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", builder.GetUser(), builder.GetPwd(), builder.GetHost(), builder.GetPort(), builder.GetDbName(), builder.GetChartSet())

	mysqlConfig := mysql.Config{
		DriverName:                "mysql",
		DSN:                       dns,   // DSN data source name
		DefaultStringSize:         255,   // string 类型字段的默认长度
		DisableDatetimePrecision:  true,  // 禁用 datetime 精度，MySQL 5.6 之前的数据库不支持
		DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式，MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
		DontSupportRenameColumn:   true,  // 用 `change` 重命名列，MySQL 8 之前的数据库和 MariaDB 不支持重命名列
		SkipInitializeWithVersion: false, // 根据版本自动配置
	}

	db, err := gorm.Open(mysql.New(mysqlConfig), gormConfig(builder.GetIsDebug(), mp.glog))
	if err != nil {
		return errors.New("数据库连接失败: " + err.Error())
	} else {
		dbss, errs := db.DB()
		if errs != nil {
			return errors.New("获取dbs实例失败: " + errs.Error())
		}
		//设置空闲连接池中的最大连接数。
		maxIdleConns := 10
		//设置与数据库的最大打开连接数
		maxOpenConns := 10
		if builder.GetMaxIdleConns() > 0 {
			maxIdleConns = builder.GetMaxIdleConns()
		}

		dbss.SetMaxIdleConns(maxIdleConns)
		dbss.SetMaxOpenConns(maxOpenConns)
		mp.conn[bHansh] = db
	}

	return nil
}

// gormConfig 根据配置决定是否开启日志
func gormConfig(mod int, glog *logrus.Logger) *gorm.Config {
	switch mod {
	case 1:
		return &gorm.Config{
			Logger:                                   logger.Default.LogMode(logger.Info),
			DisableForeignKeyConstraintWhenMigrating: true,
			SkipDefaultTransaction:                   true, // 禁用默认事务
			//配置表面后面不要带s
			NamingStrategy: schema.NamingStrategy{
				TablePrefix:   "",
				SingularTable: true,
				NameReplacer:  nil,
			},
		}
	case 2:

		return &gorm.Config{
			Logger: logger.New(
				glog,
				logger.Config{
					SlowThreshold:             time.Second, // 慢查询阈值
					LogLevel:                  logger.Info, // 设置日志级别
					IgnoreRecordNotFoundError: true,        // 忽略记录未找到的错误
					Colorful:                  false,       // 禁用日志颜色
				},
			),
			DisableForeignKeyConstraintWhenMigrating: true,
			SkipDefaultTransaction:                   true, // 禁用默认事务
			//配置表面后面不要带s
			NamingStrategy: schema.NamingStrategy{
				TablePrefix:   "",
				SingularTable: true,
				NameReplacer:  nil,
			},
		}

	case 3:
		return &gorm.Config{
			Logger:                                   logger.Default.LogMode(logger.Silent),
			DisableForeignKeyConstraintWhenMigrating: true,
			SkipDefaultTransaction:                   true, // 禁用默认事务
			//配置表面后面不要带s
			NamingStrategy: schema.NamingStrategy{
				TablePrefix:   "",
				SingularTable: true,
				NameReplacer:  nil,
			},
		}

	}
	return nil
}

func GetLogger() *logrus.Logger {
	now := time.Now()
	logFilePath := ""
	if dir, err := os.Getwd(); err == nil {
		logFilePath = dir + "/logs/sql"
	}
	if err := os.MkdirAll(logFilePath, 0777); err != nil {
		panic("创建文件夹失败:" + err.Error())
	}
	logFileName := now.Format("2006-01-02") + ".log"
	//日志文件
	fileName := path.Join(logFilePath, logFileName)
	if _, err := os.Stat(fileName); err != nil {
		if _, err := os.Create(fileName); err != nil {
			panic("创建文件失败:" + err.Error())
		}
	}
	//写入文件
	src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	if err != nil {
		panic("写入文件失败:" + err.Error())
	}

	//实例化
	logger := logrus.New()

	//设置输出
	logger.Out = src

	//设置日志级别
	logger.SetLevel(logrus.DebugLevel)

	//设置日志格式
	logger.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	return logger
}

func (mp *mysqlPool) getDb(builderName string) (*gorm.DB, error) {
	if len(strings.TrimSpace(builderName)) != 0 {
		builderNameHash := hash256(builderName)
		if v, ok := mp.conn[builderNameHash]; ok {
			return v, nil
		}
		return nil, errors.New("conn cant not in build on builder name :" + builderName)
	} else {
		if len(mp.conn) != 0 && len(mp.conn) == 1 {
			for _, v := range mp.conn {
				return v, nil
			}
		} else if len(mp.conn) == 0 {
			return nil, errors.New("Please initialize the database first")
		} else {
			return nil, errors.New("You have initialized multiple databases, please pass the database instance buildName when operating the database.")
		}
	}

	return nil, nil
}

func GetDb(buildName string) (*gorm.DB, error) {
	poll := NewMysqlPool()
	err := poll.Init()
	if err != nil {
		return nil, err
	}
	return poll.getDb(buildName)
}

/*
*
生成hash256
*/
func hash256(str string) string {
	h := sha256.New()
	h.Write([]byte(str))
	sum := h.Sum(nil)
	s := hex.EncodeToString(sum)
	return s
}

/*
*
aqt获b取当前项目录
*/
func getCurrentPath() string {
	dir, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return strings.Replace(dir, "\\", "/", -1)
}
